﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;

namespace PSE.Tools.WasConfigGenerator
{
    public class ServiceAssembliesConfigGenerator : BaseConfigGenerator
    {
        protected ServiceAssembliesConfigGenerator()
            : base()
        {
        }

        protected static ServiceAssembliesConfigGenerator _instance = null;
        protected static object _lock = new object();

        public static ServiceAssembliesConfigGenerator GetInstance()
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new ServiceAssembliesConfigGenerator();
                }

                return _instance;
            }
        }

        public override void Generate(string serviceAssembliesPath)
        {
            bool hasError = false;

            if (string.IsNullOrEmpty(serviceAssembliesPath))
                throw new ArgumentNullException("serviceAssembliesPath");

            if (!Directory.Exists(serviceAssembliesPath))
                throw new DirectoryNotFoundException(string.Format("The service assemblies path \"{0}\" doesn't exist", serviceAssembliesPath));


            // Captura as sub-pastas da pasta de origem, que são as pastas do módulos
            string[] modules = Directory.GetDirectories(serviceAssembliesPath);

            if (modules.Length == 0)
            {
                OnError(new DirectoryNotFoundException(string.Format("The service assemblies path \"{0}\" doesn't have sub-foldes", serviceAssembliesPath)));
                hasError = true;
            }

            ShowInformation("Generating service assemblies configuration...\n");

            bool generated = false;

            foreach (string m in modules)
            {
                try
                {
                    // Capturando pasta do módulo
                    string module = Path.Combine(m, "Bin");

                    // Se não existir pasta Bin vai para o próxima pasta
                    if (!Directory.Exists(module))
                        continue;

                    // Para cada pasta do módulo captura os arquivos config
                    string[] configs = Directory.GetFiles(module, "*.dll.config");

                    // Se não existir configs, vai para próxima pasta
                    if (configs.Length <= 0)
                        continue;

                    generated = true;

                    ShowStep("Generating configuration for module \"{0}\"...", Path.GetFileName(m));

                    // Capturando pasta de destino
                    string dest = m;

                    // Cria web.config
                    XmlDocument webConfig = new XmlDocument();

                    foreach (string config in configs)
                    {
                        try
                        {
                            // Abrindo arquivo config
                            XmlDocument configFile = new XmlDocument();
                            configFile.Load(config);

                            if (webConfig.SelectSingleNode("/configuration") == null &&
                                configFile.SelectSingleNode("/configuration") != null)
                            {
                                AppendChild(webConfig, configFile.SelectSingleNode("/configuration"));
                            }
                            else
                            {
                                foreach (XmlNode element in configFile.SelectNodes("/configuration/*"))
                                {
                                    #region configuration
                                    // Percorre todas as tag do config e adicina no web.config se
                                    // a tag não existir
                                    if (webConfig.SelectSingleNode("/configuration/" + element.Name) == null)
                                    {
                                        AppendChild(webConfig.SelectSingleNode("/configuration"), element);
                                    }
                                    #endregion

                                    #region configSections
                                    // Se a tag existir faz merge dos elementos
                                    else if (element.Name == "configSections")
                                    {
                                        foreach (XmlNode section in element.ChildNodes)
                                        {
                                            if (section.Name == "section" && section.Attributes != null && section.Attributes["name"] != null &&
                                                webConfig.SelectSingleNode("/configuration/configSections/section[@name='" + section.Attributes["name"].Value + "']") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/configSections"), section);
                                            }
                                        }
                                    }
                                    #endregion

                                    #region appSettings
                                    else if (element.Name == "appSettings")
                                    {
                                        foreach (XmlNode setting in element.ChildNodes)
                                        {
                                            if (setting.Name == "add" && setting.Attributes != null && setting.Attributes["key"] != null &&
                                                webConfig.SelectSingleNode("/configuration/appSettings/add[@key='" + setting.Attributes["key"].Value + "']") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/appSettings"), setting);
                                            }
                                        }

                                    }
                                    #endregion

                                    #region system.serviceModel
                                    else if (element.Name == "system.serviceModel")
                                    {
                                        #region Behaviors
                                        // Behaviors
                                        XmlNode behaviors = element.SelectSingleNode("behaviors");

                                        if (behaviors != null)
                                        {
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), behaviors);
                                            }
                                            else
                                            {
                                                // Service Behaviors
                                                XmlNode serviceBehaviors = behaviors.SelectSingleNode("serviceBehaviors");

                                                if (serviceBehaviors != null)
                                                {
                                                    if (webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors") == null)
                                                    {
                                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors"), serviceBehaviors);
                                                    }
                                                    else
                                                    {
                                                        foreach (XmlNode behavior in serviceBehaviors.ChildNodes)
                                                        {
                                                            if (behavior.Name == "behavior" && behavior.Attributes != null && behavior.Attributes["name"] != null &&
                                                                webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors/behavior[@name='" + behavior.Attributes["name"].Value + "']") == null)
                                                            {
                                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/behaviors/serviceBehaviors"), behavior);
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        #endregion

                                        #region Bindings
                                        // Bindings
                                        XmlNode bindings = element.SelectSingleNode("bindings");

                                        if (bindings != null)
                                        {
                                            // Verifica se a tag bindings já existe no arquivo, se não existir adiciona a tag completa
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), bindings);
                                            }
                                            else
                                            {
                                                // Se já existir, faz merge dos bindings
                                                foreach (XmlNode customBinding in bindings.ChildNodes)
                                                {
                                                    if (customBinding.NodeType != XmlNodeType.Comment)
                                                    {
                                                        if (webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name) == null)
                                                        {
                                                            AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings"), customBinding);
                                                        }
                                                        else
                                                        {
                                                            foreach (XmlNode binding in customBinding.ChildNodes)
                                                            {
                                                                if (binding.Name == "binding" && binding.Attributes != null && binding.Attributes["name"] != null &&
                                                                    webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name + "/binding[@name='" + binding.Attributes["name"].Value + "']") == null)
                                                                {
                                                                    AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/bindings/" + customBinding.Name), binding);
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        #endregion

                                        #region Services
                                        // Services
                                        XmlNode services = element.SelectSingleNode("services");

                                        if (services != null)
                                        {
                                            // Verifica se a tag services já existe no arquivo, se não existir adiciona a tag completa
                                            if (webConfig.SelectSingleNode("/configuration/system.serviceModel/services") == null)
                                            {
                                                AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel"), services);
                                            }
                                            else
                                            {
                                                foreach (XmlNode service in services.ChildNodes)
                                                {
                                                    if (service.Name == "service" && service.Attributes != null && service.Attributes["name"] != null &&
                                                        webConfig.SelectSingleNode("/configuration/system.serviceModel/services/service[@name='" + service.Attributes["name"].Value + "']") == null)
                                                    {
                                                        AppendChild(webConfig.SelectSingleNode("/configuration/system.serviceModel/services"), service);
                                                    }
                                                }
                                            }
                                        }
                                        #endregion
                                    }
                                    #endregion
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            OnError(new InvalidOperationException(string.Format("There was an error processing file \"{0}\"", config), ex));
                            hasError = true;
                        }
                    }


                    // Salva web.config
                    string webConfigFileName = Path.Combine(dest, "web.config");
                    webConfig.Save(webConfigFileName);

                    if(!hasError)
                        ShowSuccess("Configuration file \"{0}\" has been generated successfully", webConfigFileName);

                    // Criando arquivo svc
                    ShowStep("Generating service files...");

                    XmlNode servicesSvc = webConfig.SelectSingleNode("/configuration/system.serviceModel/services");

                    if (servicesSvc != null)
                    {
                        foreach (XmlNode service in servicesSvc.ChildNodes)
                        {
                            try
                            {
                                if (service.Name == "service" && service.Attributes != null && service.Attributes["name"] != null)
                                {
                                    File.WriteAllText(Path.Combine(dest, GetServiceName(service.Attributes["name"].Value) + ".svc"), "<% @ServiceHost Service=\"" + service.Attributes["name"].Value + "\" %>", Encoding.UTF8);
                                }
                            }
                            catch (Exception ex)
                            {
                                OnError(new InvalidOperationException(string.Format("There was an error generating service \"{0}\"", service.Name), ex));
                                hasError = true;
                            }
                        }
                    }

                    if(!hasError)
                        ShowSuccess("Service files has been generated successfully\n");
                }
                catch (Exception ex)
                {
                    OnError(new InvalidOperationException(string.Format("There was an error processing module \"{0}\"", m), ex));
                    hasError = true;
                }
            }


            if (!generated)
                OnError(new FileNotFoundException(string.Format("No configuration files found in service assemblies path \"{0}\"", serviceAssembliesPath)));
        }

        private void AppendChild(XmlNode parentNode, XmlNode childNode)
        {
            XmlDocument document = parentNode as XmlDocument ?? parentNode.OwnerDocument;

            XmlNode newNode = document.CreateElement(childNode.Name);
            newNode.InnerXml = childNode.InnerXml;

            if (childNode.Attributes != null)
            {
                foreach (XmlAttribute att in childNode.Attributes)
                {
                    XmlAttribute newAttribute = document.CreateAttribute(att.Name);
                    newAttribute.Value = att.Value;

                    newNode.Attributes.Append(newAttribute);
                }
            }

            parentNode.AppendChild(newNode);
        }

        private string GetServiceName(string serviceName)
        {
            string[] arr = serviceName.Split('.');
            if (arr.Length <= 3)
                OnError(new IndexOutOfRangeException("Invalid service name '" + serviceName + "'"));

            if (serviceName.ToLower().Contains("imercado"))
            {
                return arr[6];
            }
            else if (serviceName.ToLower().Contains("pse"))
            {
                return arr[3];
            }
            else if (serviceName.ToLower().Contains("sinacor"))
            {
                return string.Format("{0}.{1}", arr[3], arr[arr.Length - 1]);
            }
            else
            {
                throw new Exception("Cannot find system name");
            }
        }
    }
}
